package store.lunangangster.engine.impl.bpmn.behavior;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import store.lunangangster.bpmn.model.ParallelGateway;
import store.lunangangster.bpmn.model.SequenceFlow;
import store.lunangangster.engine.impl.Context;
import store.lunangangster.engine.impl.agenda.NodeFlowEngineAgenda;
import store.lunangangster.engine.impl.entity.ExecutionEntity;
import store.lunangangster.engine.impl.entity.mgr.ExecutionEntityManager;

public class ParallelGatewayBehavior extends GatewayBehavior {

  protected final AtomicInteger lock;
  protected final Set<String> incomings;
  protected final Map<String, Boolean> incomingsMap;

  public ParallelGatewayBehavior(Set<String> incomings) {
    this.incomings = incomings;
    this.lock = new AtomicInteger(incomings.size());
    this.incomingsMap = new HashMap<>(incomings.size());
    initMap();
  }

  private void initMap() {
    for (String incoming : incomings) {
      this.incomingsMap.put(incoming, false);
    }
  }

  @Override
  public void doBehavior(ExecutionEntity execution) {
    ParallelGateway parallelGateway = (ParallelGateway) execution.getCurrentFlowElement();
    // 这里不需要考虑顺序，只需要考虑同步量达标即可
    if (lock.get() == 0) {
      // ignore ConditionExpression
      List<SequenceFlow> outgoings = parallelGateway.getOutgoing();
      NodeFlowEngineAgenda agenda = Context.getCommandContext().getAgenda();
      ExecutionEntityManager executionManager = Context.getProcessEngineConfiguration()
          .getExecutionEntityManager();

      SequenceFlow firstSequenceFlow = outgoings.get(0);
      execution.setCurrentFlowElement(firstSequenceFlow);
      agenda.addDoElementBehaviorOperation(execution, parallelGateway);

      for (int i = 1; i < outgoings.size(); i++) {
        ExecutionEntity brotherExecution = executionManager.createBrotherExecution(execution);
        brotherExecution.setCurrentFlowElement(outgoings.get(i));
        agenda.addDoElementBehaviorOperation(brotherExecution, parallelGateway);
      }
    }
  }

  /**
   * 该方法用于注册到达ParallelGateway的SequenceFlow，当所有incomings到达后，并行网关完成同步，
   * 即将进行输出
   * @param sequenceFlow  当前到来的incoming
   * @return  当且仅当sequenceFlow是第一次到达并行网关时，返回true，表示注册成功
   */
  public boolean registerIncoming(SequenceFlow sequenceFlow) {
    // 当sequenceFlow不在incomings列表里，或已经注册过了，则不再进行注册
    if (lock.get() > 0 && !incomingsMap.getOrDefault(sequenceFlow.getId(), true)) {
      // 这里虽然会有并发访问，但是不会存在线程安全问题，因为结果肯定是将该标志设置为true
      incomingsMap.put(sequenceFlow.getId(), true); // set True
      lock.decrementAndGet();
      return true;
    }
    return false;
  }
}
